/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package rede;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import neuronio.ClasseNeuronio;
import neuronio.Neuronio;
import util.ArquivoUtil;

/**
 *
 * @author Emanoel
 */
public class Rede {

    private Neuronio[][] neuronios;
    private double[] entradas;
    private double taxaAprendizado;
    private int xVencedor;
    private int yVencedor;
    private Random rand;
    private double tau1;
    private double tau2 = 1000;
    private double eta0 = 0.1;
    private double sigma0;
    private int LIMITE_ORDENACAO = 1000;

    public Rede(int tamanhox, int tamanhoy, int qtdEntradas) {
        int j;
        neuronios = new Neuronio[tamanhox][tamanhoy];
        entradas = new double[qtdEntradas];
        xVencedor = yVencedor = 0;

        for (int i = 0; i < neuronios.length; i++) {
            for (j = 0; j < neuronios[i].length; j++) {
                neuronios[i][j] = new Neuronio(entradas.length);
            }
        }
        sigma0 = tamanhox < tamanhoy ? tamanhox : tamanhoy;
        tau1 = 1000.0 / Math.log10(sigma0);
    }

    public void acharVencedor() throws InterruptedException {
        int j;
        int i;
        double maior = Double.MIN_VALUE;
        int tamanhox, tamanhoy;

        maior = Double.MIN_VALUE;
        tamanhox = neuronios.length;

        calcularRede();
        for (i = 0; i < tamanhox; i++) {
            tamanhoy = neuronios[i].length;
            for (j = 0; j < tamanhoy; j++) {
                if (maior < neuronios[i][j].getSaidaRede()) {
                    maior = neuronios[i][j].getSaidaRede();
                    xVencedor = i;
                    yVencedor = j;
                }
            }
        }
    }

    public void acharVencedor(double[] entrada) {
        int j;
        int i;
        double menor;
        int tamanhox, tamanhoy;

        menor = Double.MAX_VALUE;
        tamanhox = neuronios.length;

        calcularRede(entrada);
        for (i = 0; i < tamanhox; i++) {
            tamanhoy = neuronios[i].length;
            for (j = 0; j < tamanhoy; j++) {
                if (neuronios[i][j].getSaidaRede() < menor) {
                    menor = neuronios[i][j].getSaidaRede();
                    xVencedor = i;
                    yVencedor = j;
                }
            }
        }
    }

    private void calcularRede() {
        int i, j;
        int tamanhox, tamanhoy;

        tamanhox = neuronios.length;

        for (i = 0; i < tamanhox; i++) {
            Neuronio[] neuroniosA = neuronios[i];
            tamanhoy = neuroniosA.length;
            for (j = 0; j < tamanhoy; j++) {
                neuroniosA[j].calcularSaida(entradas);
            }
        }
    }

    private void calcularRede(double[] entrada) {
        int i, j;
        int tamanhox, tamanhoy;

        tamanhox = neuronios.length;

        for (i = 0; i < tamanhox; i++) {
            Neuronio[] neuroniosA = neuronios[i];
            tamanhoy = neuroniosA.length;
            for (j = 0; j < tamanhoy; j++) {
                neuroniosA[j].calcularSaida(entrada);
            }
        }
    }

    public double[] gerarPesos(int qtd) {
        if (rand == null) {
            rand = new Random(System.currentTimeMillis());
        }
        double[] retorno = new double[qtd];
        for (int i = 0; i < qtd; i++) {
            retorno[i] = rand.nextDouble();
        }
        return retorno;
    }

    // Calcula a distancia ao quadrado entre um ponto (x1,y1) e um ponto (x2,y2)
    public double distancia(int x1, int y1, int x2, int y2) {
        double valorX = x1 > x2 ? x1 - x2 : x2 - x1;
        double valorY = y1 > y2 ? y1 - y2 : y2 - y1;
        return (Math.pow(valorX, 2) + Math.pow(valorY, 2));
    }

    public double sigma(int tempo) {
        return (sigma0 * Math.exp(-(((double) tempo) / tau1)));
    }

    public double vizinhanca(int x1, int y1, int x2, int y2, int tempo) {
        double numerador = -(distancia(x1, y1, x2, y2));
        double denominador = 2 * Math.pow(sigma(tempo), 2);

        return Math.exp(numerador / denominador);
    }

    public void atualizaTaxaAprendizado(int tempo) {
        double valor = eta0 * Math.exp(-(((double) tempo) / tau2));
        //taxaAprendizado = valor < 0.01 ? 0.01 : valor;
        taxaAprendizado = valor;

    }

    public void atualizaPesos(int tempo) {
        double[] pesos = null;
        int i, j, k;
        int tamanhox, tamanhoy;
        double temp;

        tamanhox = neuronios.length;

        for (i = 0; i < tamanhox; i++) {
            tamanhoy = neuronios[i].length;
            for (j = 0; j < tamanhoy; j++) {
                pesos = neuronios[i][j].getPesos();
                //vizinhanca = vizinhanca(xVencedor, yVencedor, i, j, tempo);
                temp = taxaAprendizado * vizinhanca(xVencedor, yVencedor, i, j, tempo);
                for (k = 0; k < pesos.length; k++) {
                    pesos[k] = pesos[k] + temp * (entradas[k] - pesos[k]);
                }
            }
        }
    }

    public ClasseNeuronio[] getResposta(String arquivoTreinamento) throws InterruptedException {
        double[][] padroes = ArquivoUtil.converteBase(arquivoTreinamento);
        HashMap<String, String> classes = ArquivoUtil.obterClasses(arquivoTreinamento);
        ClasseNeuronio[] retorno;
        int tamanho = padroes.length;
        int i;

        retorno = new ClasseNeuronio[tamanho];

        for (i = 0; i < tamanho; i++) {
            entradas = padroes[i];
            acharVencedor();
            retorno[i] = new ClasseNeuronio(xVencedor, yVencedor, classes.get(ArquivoUtil.converter(entradas)));
        }

        return (retorno);
    }

    public void treinamento(String arquivoTreinamento) {
        String texto;
        System.out.println("Iniciando Treinamento...");
        int tempo = 0;
        int i, j;
        int qtdPadroes;

        i = j = 0;

        try {
            double[][] padroes = ArquivoUtil.converteBase(arquivoTreinamento);
            qtdPadroes = padroes.length;
            // fase de ordenacao
            System.out.println("Iniciando Fase de Ordenacao...");
            for (i = 0; i < LIMITE_ORDENACAO; i++) {
                tempo++;
                for (j = 0; j < padroes.length; j++) {
                    entradas = padroes[j];
                    acharVencedor();
                    atualizaPesos(tempo);
                }

                atualizaTaxaAprendizado(tempo);
            }

            // fase de convergencia
            System.out.println("Taxa de aprendizagem ao fim da fase de ordenacao: " + taxaAprendizado);
            System.out.println("Iniciando Fase de Convergencia...");
            double limite = 1000 * neuronios.length * neuronios[0].length;
            for (i = 0; i < limite; i++) {
                tempo++;
                for (j = 0; j < qtdPadroes; j++) {
                    entradas = padroes[j];
                    acharVencedor();
                    atualizaPesos(tempo);
                }
                atualizaTaxaAprendizado(tempo);
                if (taxaAprendizado <= 10E-12) {
                    texto = "";
                    Calendar d = GregorianCalendar.getInstance();
                    texto += "Base de Dados: " + arquivoTreinamento + "\n";
                    texto += "Data: " + d.get(GregorianCalendar.DAY_OF_MONTH) + "/" + d.get(GregorianCalendar.MONTH) + "/" + d.get(GregorianCalendar.YEAR) + " " + d.get(GregorianCalendar.HOUR_OF_DAY) + ":" + d.get(GregorianCalendar.MINUTE) + ":" + d.get(GregorianCalendar.SECOND) + "\n";
                    texto += "Quantidade de Neuronios: " + (neuronios.length*neuronios.length) + "\n";
                    texto += "Quantidade de Padroes: " + qtdPadroes + "\n";
                    texto += "Quantidade de Iteracoes: " + tempo + "\n\n";                    
                    ArquivoUtil.adicionarLog(texto);
                    break;
                }
            //System.out.println("Atualizacao de taxa de aprendizagem: " + tempo);
            //System.out.println("Taxa de aprendizagem ao fim da fase de ordenacao: " + taxaAprendizado);
            //System.out.println("i => limite" + i + " => " + limite);
            }
        } catch (InterruptedException ex) {
            Logger.getLogger(Rede.class.getName()).log(Level.SEVERE, null, ex);
        }
        System.out.println("Taxa de aprendizagem ao fim da fase de convergencia: " + taxaAprendizado);
        System.out.println("Treinamento Finalizado!");
    }

    public Neuronio[][] getNeuronios() {
        return neuronios;
    }

    public int getXVencedor() {
        return xVencedor;
    }

    public int getYVencedor() {
        return yVencedor;
    }

    public double getTaxaAprendizado() {
        return taxaAprendizado;
    }
}

